package groep1.graph.graphstrategy.line;

import groep1.graph.GraphMediator;
import groep1.graph.GraphObject;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import bluej.debugmgr.inspector.FieldInfo;

/**
 * Implements a strategy to place the lines between the given objects.
 * 
 * @author Niels Billen
 * @version 0.1
 * @since 0.1
 */
public class NaiveLineStrategy implements LineStrategy {
	/**
	 * Creates a new line strategy.
	 */
	public NaiveLineStrategy() {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see groep1.graph.graphstrategy.GraphStrategy#setMediator(groep1.graph.
	 * GraphMediator)
	 */
	@Override
	public void setMediator(GraphMediator mediator) {
		if (mediator == null)
			throw new NullPointerException("The given mediator is null!");
		if (mediator.getLineStrategy() != this)
			throw new IllegalStateException(
					"The mediator does not have this strategy as it's line strategy!");
		this.mediator = mediator;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see groep1.graph.graphstrategy.GraphStrategy#getMediator()
	 */
	public GraphMediator getMediator() {
		return mediator;
	}

	/**
	 * The mediator of this strategy.
	 */
	private GraphMediator mediator;

	/*
	 * (non-Javadoc)
	 * 
	 * @see groep1.graph.line.LineStrategy#connect(java.util.Map)
	 */
	@Override
	public void connect(Collection<GraphObject> collection) {
		lines.clear();

		// Construct a HashMap for fast lookup of object names.
		HashMap<String, GraphObject> objects = new HashMap<String, GraphObject>();

		for (GraphObject object : collection)
			objects.put("" + object.getId() + ":" + object.getPackage()
					+ object.getClassName() + "." + object.getObjectName(),
					object);

		try {
			for (GraphObject object : objects.values())
				for (FieldInfo info : object.getFieldInfo())
					if (objects.containsKey(info.getValue()))
						lines.add(createLine(object,
								objects.get(info.getValue()), info, objects));
		} catch (IllegalArgumentException e) {
			System.out.println("Caught the exception with message "
					+ e.getMessage());
			getMediator().relayout(64, 64);
			return;
		}

		int minX = Integer.MAX_VALUE;
		int minY = Integer.MAX_VALUE;
		int maxX = Integer.MIN_VALUE;
		int maxY = Integer.MIN_VALUE;

		for (Line line : lines) {
			for (Point point : line.getPoints()) {
				minX = Math.min(minX, point.x);
				maxX = Math.max(maxX, point.x);
				minY = Math.min(minY, point.y);
				maxY = Math.max(maxY, point.y);
			}
		}

		mediator.dimensionChanged(maxX - minX + 64, maxY - minY + 64);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see groep1.graph.graphstrategy.line.LineStrategy#getLines()
	 */
	public Collection<Line> getLines() {
		return lines;
	}

	/**
	 * Creates a line from the given object to the other given object, starting
	 * from the field with the given field info and avoiding all other given
	 * graph objects.
	 * 
	 * @param fromObj
	 *            The object for the line to start from.
	 * @param toObj
	 *            The object for the line to end.
	 * @param field
	 *            The field to start from.
	 * @return a line between the field and the referring object.
	 */
	protected Line createLine(GraphObject fromObj, GraphObject toObj,
			FieldInfo field, Map<String, GraphObject> objects) {

		Point fromPoint = new Point(fromObj.getOutgoingCoordinateOfField(field));
		fromPoint.y = (fromPoint.y / vGrid) * vGrid;

		Point toPoint = new Point(toObj.getIncomingCoordinate());
		toPoint.y = (toPoint.y / vGrid) * vGrid;

		int pointRightX = (int) (fromPoint.x / hGrid + 0.5) * hGrid + 8;
		int pointLeftX = (int) (toPoint.x / hGrid - 0.5) * hGrid - 8;

		int downY = (Math.max(fromObj.getPosition().y + fromObj.getHeight(),
				toObj.getPosition().y + toObj.getHeight()) / hGrid) * hGrid + 8;

		Point rightPoint;
		Point leftPoint;
		Point connectPointRight;
		Point connectPointLeft;
		Line[] array = new RectangularLine[5];

		boolean isValid;

		do {
			isValid = true;
			rightPoint = new Point(pointRightX, fromPoint.y);
			leftPoint = new Point(pointLeftX, toPoint.y);
			connectPointRight = new Point(pointRightX, downY);
			connectPointLeft = new Point(pointLeftX, downY);

			array[0] = new RectangularLine(fromPoint, rightPoint);
			array[1] = new RectangularLine(rightPoint, connectPointRight);
			array[2] = new RectangularLine(connectPointLeft, connectPointRight);
			array[3] = new RectangularLine(connectPointLeft, leftPoint);
			array[4] = new RectangularLine(leftPoint, toPoint);

			search: for (int i = 0; i < array.length - 1; i++) {
				for (GraphObject object : objects.values()) {
					if (array[i].overlap(object, 8)) {
						if ((i == 0 && object != fromObj)
								|| (i == array.length - 1 && object != toObj))
							throw new IllegalArgumentException(
									"Relayout requested!");
						if ((i == 0 && object == fromObj)
								|| (i == array.length - 1 && object == toObj))
							continue;
						isValid = false;
						if (i < 2)
							pointRightX += hGrid;
						if (i == 2)
							downY += vGrid;
						if (i > 2)
							pointLeftX -= hGrid;
						break search;
					}
				}

				for (Line line : lines) {
					if (line.overlap(array[i])) {
						isValid = false;
						if (i < 2)
							pointRightX += hGrid;
						if (i == 2)
							downY += vGrid;
						if (i > 2)
							pointLeftX -= hGrid;
						break search;
					}
				}
			}
		} while (!isValid);

		return new RectangularLine(fromPoint, rightPoint, connectPointRight,
				connectPointLeft, leftPoint, toPoint);
	}

	/**
	 * The horizontal field.
	 */
	protected int hGrid = 4;

	/**
	 * The vertical field.
	 */
	protected int vGrid = 4;

	/**
	 * List with the lines
	 */
	private ArrayList<Line> lines = new ArrayList<Line>();
}
